package zdm.core;

import zdm.annotation.*;
import zdm.annotation.aop.ZdmAround;
import zdm.annotation.aop.ZdmAspect;
import zdm.annotation.aop.ZdmBefore;

import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;

/**
 * 创建bean的上下文
 *
 * @Author: 张德明
 * @CreateTime: 2025-06-17 17:18:08
 * @Description: bean上下文，主要用于bean实例化，注入等
 */

public class ApplicationContext {

    /**
     * AOP新增:
     * 创建一个列表，专门用来存放扫描到的切面类的Class对象。
     * 这样我们后面就知道哪些类是用来执行AOP逻辑的。
     */
    private List<Class<?>> aspectClasses = new ArrayList<>();

    public void init(String basePackage) throws Exception {
        // 阶段一：扫描类并实例化Bean
        initBeans(basePackage);

        // 阶段二：AOP
        applyAop();

        // 阶段三：执行依赖注入
        performDependencyInjection();


    }

    // 首先需要初始化，根据包名扫描所有的类
    private void initBeans(String basePackage) throws Exception {
        // 拿到所有的类的class对象
        List<Class<?>> classes = scanPackage(basePackage);
        for (Class<?> clazz : classes) {
            // 如果类上面用了@Controller或者@Service注解，则创建该类的实例
            if (clazz.isAnnotationPresent(ZdmController.class) || clazz.isAnnotationPresent(ZdmService.class)) {
                // 通过无参构造创建实例
                Object instance = clazz.getDeclaredConstructor().newInstance();
                // 获取类名作为beanName
                String beanName = determineBeanName(clazz);

                // 判断Bean名称是否冲突
                if (BeanFactory.getBean(beanName) != null) {
                    throw new RuntimeException("Bean " + beanName + "已经存在，请检查你的代码");
                }
                // 将实例添加到BeanFactory中
                BeanFactory.addBean(beanName, instance);
                // 如果是Service实现类，还要注册接口类型
                if (clazz.isAnnotationPresent(ZdmService.class)) {
                    Class<?>[] interfaces = clazz.getInterfaces();
                    for (Class<?> interfaceClass : interfaces) {
                        String interfaceName = interfaceClass.getSimpleName();
                        BeanFactory.addBean(interfaceName, instance);
                    }
                }
            }
            if (clazz.isAnnotationPresent(ZdmAspect.class)) {
                aspectClasses.add(clazz);
            }
        }


        // 扫描依赖注入，只有已经注册成bean的实例才能进行依赖注入,先拿到所有的实例
//        for (Object instance : BeanFactory.getAllBeans().values()) {
//            // 获取类中的所有字段
//            Field[] fields = instance.getClass().getDeclaredFields();
//            for (Field field : fields) {
//                // 如果字段上面有@ZdmAutowired注解,则进行依赖注入
//                if (field.isAnnotationPresent(ZdmAutowired.class)) {
//                    // 获取字段的类型，根据类型名字进行搜寻注入
//                    String beanName = field.getType().getSimpleName();
//                    Object bean = BeanFactory.getBean(beanName);
//                    if (bean == null) {
//                        throw new RuntimeException("注入时未找到bean: " + beanName);
//                    }
//
//                    // 一般是私有字段，所以需要设置访问权限
//                    field.setAccessible(true);
//                    // 将bean注入到实例中
//                    try {
//                        field.set(instance, bean);
//                    } catch (IllegalAccessException e) {
//                        throw new RuntimeException("无法注入bean: " + beanName + "到字段: " + field.getName(), e);
//                    }
//                }
//            }
//        }


    }

    private void applyAop() throws Exception {
        // 拿到之前标志上的注解切面
        for (Class<?> aspectClass : aspectClasses) {
            // 创建这个切面类的实例
            Object instance = aspectClass.getDeclaredConstructor().newInstance();

            // 找到Before 方法
            for (Method method : aspectClass.getMethods()) {
                // 拿到这个方法上的Before注解
                if (method.isAnnotationPresent(ZdmBefore.class)) {
                    ZdmBefore before = method.getAnnotation(ZdmBefore.class);
                    String pointcut = before.value();

                    // 拿到目标注解的class对象
                    String targetAnnotationStr = pointcut.substring(1);
                    Class<? extends Annotation> targetAnnotation = (Class<? extends Annotation>) Class.forName(targetAnnotationStr);

                    // 遍历所有的Bean工厂中的对象，检查是否需要被代理
                    for (String beanName : BeanFactory.getAllBeanNames()) {
                        Object bean = BeanFactory.getBean(beanName);
                        // 由于jdk动态代理要求实现接口，所以加判断
                        if (bean != null && bean.getClass().getInterfaces().length > 0) {
                            // 检查某个方法上是否存在目标注解
                            for (Method beanMethod : bean.getClass().getMethods()) {
                                if (beanMethod.isAnnotationPresent(targetAnnotation)) {
                                    System.out.println("[AOP]发现需要代理的Bean：" + beanName +
                                            "，方法：" + beanMethod.getName());

                                    AopInvocationHandler handler = new AopInvocationHandler(
                                            bean,
                                            instance,
                                            method,
                                            "before"
                                    );
                                    Object proxy = Proxy.newProxyInstance(
                                            bean.getClass().getClassLoader(),
                                            bean.getClass().getInterfaces(),
                                            handler
                                    );
                                    BeanFactory.addBean(beanName, proxy);

                                    for (Class<?> anInterface : bean.getClass().getInterfaces()) {
                                        BeanFactory.addBean(anInterface.getSimpleName(), proxy);
                                    }

                                    break;
                                }
                            }
                        }
                    }
                }
                if (method.isAnnotationPresent(ZdmAround.class)) {
                    ZdmAround around = method.getAnnotation(ZdmAround.class);
                    String pointcut = around.value();
                    String targetAnnotationStr = pointcut.substring(1);
                    Class<? extends Annotation> targetAnnotationClass = (Class<? extends Annotation>) Class.forName(targetAnnotationStr);

                    for (String beanName : new ArrayList<>(BeanFactory.getAllBeanNames())) {
                        Object originalBean = BeanFactory.getBean(beanName);
                        if (originalBean == null || Proxy.isProxyClass(originalBean.getClass())) continue;

                        if (originalBean.getClass().getInterfaces().length > 0) {
                            for (Method beanMethod : originalBean.getClass().getMethods()) {
                                if (beanMethod.isAnnotationPresent(targetAnnotationClass)) {
                                    System.out.println("[AOP] 发现需要Around代理的Bean: " + beanName + " (方法: " + beanMethod.getName() + ")");

                                    // 创建 Handler 时，明确指定类型为 "around"
                                    AopInvocationHandler handler = new AopInvocationHandler(originalBean, instance, method, "around");

                                    Object proxy = Proxy.newProxyInstance(
                                            originalBean.getClass().getClassLoader(),
                                            originalBean.getClass().getInterfaces(),
                                            handler
                                    );

                                    // 替换原始对象
                                    BeanFactory.addBean(beanName, proxy);
                                    for (Class<?> anInterface : originalBean.getClass().getInterfaces()) {
                                        if (BeanFactory.getBean(anInterface.getSimpleName()) != null) {
                                            BeanFactory.addBean(anInterface.getSimpleName(), proxy);
                                        }
                                    }
                                    break;
                                }
                            }
                        }
                    }
                }
            }
        }
    }

    private void performDependencyInjection() throws Exception {
        // 拿到Bean工厂所有的实例
        for (Object instance : BeanFactory.getAllBeans().values()) {
            // 拿到实例中的所有字段
            Field[] fields = instance.getClass().getDeclaredFields();
            for (Field field : fields) {
                if (field.isAnnotationPresent(ZdmAutowired.class)) {
                    Object beanForInjection = findBeanForInjection(field);
                    if (beanForInjection == null) {
                        String beanNameHint = field.isAnnotationPresent(ZdmQualifier.class)
                                ? field.getAnnotation(ZdmQualifier.class).value()
                                : field.getType().getSimpleName();
                        throw new RuntimeException("注入时未找到bean: " + beanNameHint + " 用于字段: " + field.getName() + " 在类 " + instance.getClass().getName());
                    }
                    field.setAccessible(true);
                    field.set(instance, beanForInjection);
                }
            }
        }

        // 执行初始化方法Post Construct
        for (String beanName : BeanFactory.getAllBeanNames()) {
            Object instance = BeanFactory.getBean(beanName);
            for (Method method : instance.getClass().getMethods()) {
                if (method.isAnnotationPresent(ZdmPostConstruct.class)) {
                    System.out.println("执行初始化方法: " + instance.getClass().getSimpleName() + "." + method.getName() + "()");
                    // 反射调用该方法
                    method.invoke(instance);
                }
            }
        }
    }


    private Object findBeanForInjection(Field field) {
        // 如果字段上有 @ZdmQualifier 注解，则按名称查找
        if (field.isAnnotationPresent(ZdmQualifier.class)) {
            ZdmQualifier qualifier = field.getAnnotation(ZdmQualifier.class);
            String beanName = qualifier.value();
            System.out.println("正在通过 @ZdmQualifier 按名称 [" + beanName + "] 注入字段 " + field.getName());
            return BeanFactory.getBean(beanName);
        }

        // 如果没有 @ZdmQualifier，则退回原来的按类型(接口名)查找
        String typeName = field.getType().getSimpleName();
        System.out.println("正在按类型 [" + typeName + "] 注入字段 " + field.getName());
        Object bean = BeanFactory.getBean(typeName);

        if (bean == null) {
            String defaultBeanName = typeName.substring(0, 1).toLowerCase() + typeName.substring(1);
            bean = BeanFactory.getBean(defaultBeanName);
        }

        return bean;
    }


    private String determineBeanName(Class<?> clazz) {
        String beanName = "";
        if (clazz.isAnnotationPresent(ZdmController.class)) {
            beanName = clazz.getAnnotation(ZdmController.class).value();
        } else if (clazz.isAnnotationPresent(ZdmService.class)) {
            beanName = clazz.getAnnotation(ZdmService.class).value();
        }

        // 如果注解中没有指定名称，则使用类名的首字母小写作为默认名称
        if (beanName.isEmpty()) {
            String simpleName = clazz.getSimpleName();
            beanName = simpleName.substring(0, 1).toLowerCase() + simpleName.substring(1);
        }
        return beanName;
    }


    private List<Class<?>> scanPackage(String basePackage) {
        List<Class<?>> classes = new ArrayList<>();
        // 将包名转换为文件路径
        String packagePath = basePackage.replace('.', '/');
        // 获取所有的路径
        String path = getClass().getClassLoader().getResource(packagePath).getPath();

        File directory = new File(path);
        // 如果不是目录，则返回空列表
        if (!directory.exists()) {
            return classes;
        }
        scanDirectory(directory, basePackage, classes);
        return classes;
    }

    private void scanDirectory(File directory, String basePackage, List<Class<?>> classes) {
        File[] files = directory.listFiles();
        if (files == null) {
            return;
        }
        for (File file : files) {
            if (file.isDirectory()) {
                String subPackage = basePackage + "." + file.getName();
                scanDirectory(file, subPackage, classes);
            } else if (file.getName().endsWith(".class")) {  // 不是目录是一个class文件
                String className = basePackage + "." + file.getName().replace(".class", "");
                try {
                    classes.add(Class.forName(className));
                } catch (ClassNotFoundException e) {
                    System.err.println("加载类失败: " + className + " - " + e.getMessage());
                }
            }
        }
    }
}
